package com.xq.shop.service.impl;

import com.alibaba.druid.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xq.shop.config.FileConfig;
import com.xq.shop.config.Redis;
import com.xq.shop.dao.domain.entity.Menu;
import com.xq.shop.dao.domain.entity.MyFile;
import com.xq.shop.dao.domain.entity.User;
import com.xq.shop.dao.domain.entity.UserMenu;
import com.xq.shop.dao.mapper.UserMapper;
import com.xq.shop.dto.*;
import com.xq.shop.interceptor.AuthInterceptor;
import com.xq.shop.service.IFileService;
import com.xq.shop.service.IMenuService;
import com.xq.shop.service.IUserMenuService;
import com.xq.shop.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xq.shop.utils.MyConstants;
import com.xq.shop.vo.MenuVo;
import com.xq.shop.vo.UserVo;
import enums.AccountStatus;
import enums.DataStatus;
import enums.ResultCodes;
import exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import utils.BaseConstants;
import utils.BasePage;
import utils.PasswordUtil;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author gaoxiaojin
 * @since 2024-07-16
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private final Redis redis;
    private final IFileService fileService;
    private final IUserMenuService userMenuService;
    private final IMenuService menuService;

    @Resource
    private FileConfig fileConfig;


    @Autowired
    public UserServiceImpl(Redis redis, IFileService fileService, IUserMenuService userMenuService,
                           IMenuService menuService) {
        this.redis = redis;
        this.fileService = fileService;
        this.userMenuService = userMenuService;
        this.menuService = menuService;
    }

    @Override
    public String login(LoginDto dto) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getStatus, DataStatus.NORMAL.getCode());
        queryWrapper.eq(User::getMark, DataStatus.NORMAL.getCode());
        queryWrapper.eq(User::getUsername, dto.getUsername());
        User user = getOne(queryWrapper);
        check(user, dto);
        Integer userId = user.getId();
        // 返回给用户的token
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        // 储存在redis里面的token（添加一个用户标记）
        String aToken = token + "-" + userId;
        try {
            // 查询该用户是否已经登录
            String bToken = redis.existsLike(userId);
            // 如果该用户已经处于登录状态则把之前的token对应的用户信息删掉
            if (Objects.nonNull(bToken)) {
                redis.remove(bToken);
                redis.set(bToken + "X", null, BaseConstants.X_TOKEN_TIME);
            }
            UserVo userVo = new UserVo();
            BeanUtils.copyProperties(user, userVo);
            fillInfo(user, userVo);
            redis.set(aToken, userVo, BaseConstants.TOKEN_TIME);
        } catch (Exception e) {
            log.error("redis set value error");
            throw new BusinessException(ResultCodes.REDIS_SET_VALUE_ERROR);
        }
        return token;
    }

    @Override
    public UserVo getInfo(HttpServletRequest request) {
        String token = AuthInterceptor.getToken(request);
        UserVo vo;
        try {
            token = redis.existsLike(token, 0);
            vo = (UserVo) redis.get(token);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BusinessException(ResultCodes.REDIS_GET_VALUE_ERROR);
        }
        return vo;
    }

    @Override
    public void logout(HttpServletRequest request) {
        String token = AuthInterceptor.getToken(request);
        try {
            token = redis.existsLike(token, 0);
            redis.remove(token);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BusinessException(ResultCodes.REDIS_REMOVE_VALUE_ERROR);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void editUser(EditUserDto dto, Integer userId) {

        User user = new User();
        if (dto.getId().equals(0)) {
            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(User::getUsername, dto.getUsername());
            wrapper.eq(User::getStatus, DataStatus.NORMAL.getCode());
            if (Objects.nonNull(getOne(wrapper))) {
                throw new BusinessException(ResultCodes.ACCOUNT_ALREADY_EXIST);
            }
            user.setCreateUser(userId);
            BeanUtils.copyProperties(dto, user);
            user.setPassword(PasswordUtil.enPawKey(dto.getPassword(), MyConstants.KEY1, MyConstants.KEY2, MyConstants.KEY3));
            save(user);
        } else {
            user = getById(dto.getId());
            String password = user.getPassword();
            BeanUtils.copyProperties(dto, user);
            user.setUpdateUser(userId);
            if (!StringUtils.isEmpty(dto.getPassword())) {
                user.setPassword(PasswordUtil.enPawKey(dto.getPassword(), MyConstants.KEY1, MyConstants.KEY2, MyConstants.KEY3));
            } else {
                user.setPassword(password);
            }
            updateById(user);
            Map<String, Object> map = new HashMap<>(8);
            map.put("user_id", dto.getId());
            userMenuService.removeByMap(map);
        }
        List<UserMenu> userMenuList = new ArrayList<>();
        User finalUser = user;
        dto.getMenuList().forEach(m -> {
            UserMenu userMenu = new UserMenu();
            userMenu.setUserId(finalUser.getId());
            userMenu.setMenuId(m);
            userMenu.setCreateUser(userId);
            userMenuList.add(userMenu);
        });
        userMenuService.saveBatch(userMenuList);
    }

    @Override
    public BasePage<UserVo> pageUser(QueryDto dto, UserVo userVo) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getStatus, DataStatus.NORMAL.getCode());
        if (Objects.nonNull(dto.getMark())) {
            queryWrapper.eq(User::getMark, dto.getMark());
        }
        if (Objects.nonNull(dto.getContent())) {
            queryWrapper.and(j -> j.like(User::getUsername, dto.getContent()));
        }
        queryWrapper.orderByDesc(User::getId);
        IPage<User> iPage = page(new Page<>(dto.getPage(), dto.getLimit()), queryWrapper);
        List<User> userList = iPage.getRecords();
        if (userList.isEmpty()) {
            return new BasePage<UserVo>().initPage(dto.getPage(), dto.getLimit(), 0, 0, new ArrayList<>());
        }
        return new BasePage<UserVo>().initPage(dto.getPage(), dto.getLimit(), iPage.getTotal(), iPage.getPages(), beanToVo(userList));
    }

    @Override
    public void removeUser(IntegerDto dto, Integer userId) {
        User user = getById(dto.getValue());
        user.setStatus(DataStatus.INVALID.getCode());
        user.setUpdateUser(userId);
        updateById(user);
    }

    @Override
    public void changeInfo(ChangeInfoDto dto, HttpServletRequest request) {
        String token = AuthInterceptor.getToken(request);
        User user = getById(dto.getUserId());
        String password = user.getPassword();
        if (!StringUtils.isEmpty(dto.getOldPassword())) {
            if (!password.equals(PasswordUtil.enPawKey(dto.getOldPassword(), MyConstants.KEY1, MyConstants.KEY2, MyConstants.KEY3))) {
                throw new BusinessException(ResultCodes.OLD_PASSWORD_ERROR);
            } else {
                user.setPassword(PasswordUtil.enPawKey(dto.getPassword(), MyConstants.KEY1, MyConstants.KEY2, MyConstants.KEY3));
            }
        }
        if (Objects.nonNull(dto.getHeadImage())) {
            user.setHeadImage(dto.getHeadImage());
        }
        UserVo vo = new UserVo();
        BeanUtils.copyProperties(user, vo);
        if (Objects.nonNull(user.getHeadImage())) {
            MyFile file = fileService.getById(user.getHeadImage());
            vo.setHeadImageUrl(fileConfig.montageUrl(file.getUrl()));
        }
        String aToken = token + "-" + dto.getUserId();
        fillInfo(user, vo);
        redis.set(aToken, vo);
        updateById(user);
    }

    private List<UserVo> beanToVo(List<User> userList) {
        List<UserVo> voList = new ArrayList<>();
        userList.forEach(c -> {
            UserVo vo = new UserVo();
            BeanUtils.copyProperties(c, vo);
            if (Objects.nonNull(c.getHeadImage())) {
                MyFile file = fileService.getById(c.getHeadImage());
                vo.setHeadImageUrl(fileConfig.montageUrl(file.getUrl()));
            }
            vo.setMenuList(getMenuList(c.getId()));
            User user = getById(c.getCreateUser());
            vo.setCreateUser(user.getName());
            voList.add(vo);
        });
        return voList;
    }

    /**
     * 填充用户信息
     * @param user 用户
     * @param userVo 用户vo
     */
    private void fillInfo(User user, UserVo userVo) {
        if (Objects.nonNull(user.getHeadImage())) {
            MyFile apiFile = fileService.getById(user.getHeadImage());
            userVo.setHeadImageUrl(fileConfig.montageUrl(apiFile.getUrl()));
        }
        List<MenuVo> menuVoList = getMenuList(user.getId());
        userVo.setMenuList(MyConstants.buildMenuTree(menuVoList));
        userVo.setLoginTime(new Date());
    }

    private List<MenuVo> getMenuList(Integer userId) {
        LambdaQueryWrapper<UserMenu> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserMenu::getStatus, DataStatus.NORMAL.getCode());
        wrapper.eq(UserMenu::getUserId, userId);
        List<Object> idList = userMenuService.listObjs(wrapper.select(UserMenu::getMenuId));
        List<Integer> ids = new ArrayList<>();
        idList.forEach(id -> {
            ids.add(Integer.parseInt(id.toString()));
        });
        LambdaQueryWrapper<Menu> menuWrapper = new LambdaQueryWrapper<>();
        menuWrapper.eq(Menu::getStatus, DataStatus.NORMAL.getCode());
        menuWrapper.in(Menu::getId, ids);
        List<Menu> menuList = menuService.list(menuWrapper);
        if (menuList.isEmpty()) {
            return new ArrayList<>();
        }
        List<MenuVo> menuVoList = new ArrayList<>();
        menuList.forEach(m -> {
            MenuVo vo = new MenuVo();
            BeanUtils.copyProperties(m, vo);
            menuVoList.add(vo);
        });
        return MyConstants.buildMenuTree(menuVoList);
    }

    private void check(User user, LoginDto dto) {
        if (Objects.isNull(user) || !PasswordUtil.enPawKey(dto.getPassword(), MyConstants.KEY1 , MyConstants.KEY2 , MyConstants.KEY3).equals(user.getPassword())) {
            throw new BusinessException(ResultCodes.ACCOUNT_NOT_EXIST);
        }
        if (AccountStatus.DEACTIVATE.getCode().equals(user.getMark())) {
            throw new BusinessException(ResultCodes.ACCOUNT_DEACTIVATE);
        }
    }
}
